package com.zbangmall.util.crypt.asymmetric;


import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RsaCrypt {

	//region ****************************************************** 加密 ******************************************************

	/**
	 * RSA 私钥加密(字符串)
	 */
	public static String encryptByPrivateKey(String privateKeyStr, String str) {
		PrivateKey privateKey = privateKeyStr2PrivateKey(privateKeyStr);
		return encryptByPrivateKey(privateKey, str);
	}

	/**
	 * RSA 私钥加密(对象)
	 */
	public static String encryptByPrivateKey(PrivateKey privateKey, String str) {
		try {
			byte[] bytes = str.getBytes();

			// 【1】Cipher
			Cipher cipher = Cipher.getInstance("RSA");
			// 【2】加密模式
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			// 【3】加密(由于RSA最多加密117个字节，所以采用分段加密的方式)
			byte[] encryptBytes = sliceHandler(cipher, bytes, 117);

			return Base64.getEncoder().encodeToString(encryptBytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * RSA 公钥加密(字符串)
	 */
	public static String encryptByPublicKey(String publicKeyStr, String str) {
		PublicKey publicKey = publicKeyStr2PublicKey(publicKeyStr);
		return encryptByPublicKey(publicKey, str);
	}

	/**
	 * RSA 公钥加密(对象)
	 */
	public static String encryptByPublicKey(PublicKey publicKey, String str) {
		try {
			byte[] bytes = str.getBytes();

			// 【1】Cipher
			Cipher cipher = Cipher.getInstance("RSA");
			// 【2】加密模式
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			// 【3】加密(由于RSA最多加密117个字节，所以采用分段加密的方式)
			byte[] encryptBytes = sliceHandler(cipher, bytes, 117);

			return Base64.getEncoder().encodeToString(encryptBytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	//endregion ****************************************************** 加密 ******************************************************


	//region ****************************************************** 解密 ******************************************************

	/**
	 * RSA 私钥解密(私钥字符串)
	 */
	public static String decryptByPrivateKey(String privateKeyStr, String str) {
		PrivateKey privateKey = privateKeyStr2PrivateKey(privateKeyStr);
		return decryptByPrivateKey(privateKey, str);
	}

	/**
	 * RSA 私钥解密(私钥对象)
	 */
	public static String decryptByPrivateKey(PrivateKey privateKey, String str) {
		try {
			byte[] bytes = Base64.getDecoder().decode(str);

			// 【1】Cipher
			Cipher cipher = Cipher.getInstance("RSA");
			// 【2】解密模式
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			// 【3】解密(由于RSA最多解密256个字节，所以采用分段解密的方式)
			byte[] decryptBytes = sliceHandler(cipher, bytes, 256);

			return new String(decryptBytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * RSA 公钥解密(公钥字符串)
	 */
	public static String decryptByPublicKey(String publicKeyStr, String str) {
		PublicKey publicKey = publicKeyStr2PublicKey(publicKeyStr);
		return decryptByPublicKey(publicKey, str);
	}

	/**
	 * RSA 公钥解密(公钥对象)
	 */
	public static String decryptByPublicKey(PublicKey publicKey, String str) {
		try {
			byte[] bytes = Base64.getDecoder().decode(str);

			// 【1】Cipher
			Cipher cipher = Cipher.getInstance("RSA");
			// 【2】解密模式
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			// 【3】解密(由于RSA最多解密256个字节，所以采用分段解密的方式)
			byte[] decryptBytes = sliceHandler(cipher, bytes, 256);

			return new String(decryptBytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	//endregion ****************************************************** 解密 ******************************************************


	//region ****************************************************** 工具方法 ******************************************************

	/**
	 * 使用示例：
	 * KeyPair    keyPair    = KeyPairGenerator.getInstance("RSA").generateKeyPair();
	 * PrivateKey privateKey = keyPair.getPrivate();
	 * PublicKey  publicKey  = keyPair.getPublic();
	 * String     privateStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
	 * String     publicStr  = Base64.getEncoder().encodeToString(publicKey.getEncoded());
	 *
	 * @return 返回秘钥对，包含公钥和私钥
	 */
	public static KeyPair getKeyPair() {
		try {
			return KeyPairGenerator.getInstance("RSA").generateKeyPair();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 获取公钥
	 */
	public static String getPublicKey(KeyPair keyPair) {
		PublicKey publicKey = keyPair.getPublic();
		return Base64.getEncoder().encodeToString(publicKey.getEncoded());
	}

	/**
	 * 获取私钥
	 */
	public static String getPrivateKey(KeyPair keyPair) {
		PrivateKey privateKey = keyPair.getPrivate();
		return Base64.getEncoder().encodeToString(privateKey.getEncoded());
	}

	/**
	 * 私钥字符串转私钥对象
	 */
	public static PrivateKey privateKeyStr2PrivateKey(String privateKeyStr) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[]     bytes      = Base64.getDecoder().decode(privateKeyStr);
			return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 公钥字符串转公钥对象
	 */
	public static PublicKey publicKeyStr2PublicKey(String publicKeyStr) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[]     bytes      = Base64.getDecoder().decode(publicKeyStr);
			return keyFactory.generatePublic(new X509EncodedKeySpec(bytes));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 分片(加密/解密)
	 */
	private static byte[] sliceHandler(Cipher cipher, byte[] bytes, int maxSize) {
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		try {
			byte[] buffer;
			int    offset      = 0;
			int    bytesLength = bytes.length;
			while (bytesLength - offset > 0) {
				if (bytesLength - offset >= maxSize) {
					buffer = cipher.doFinal(bytes, offset, maxSize);
					offset += maxSize;
				} else {
					buffer = cipher.doFinal(bytes, offset, bytesLength - offset);
					offset = bytesLength;
				}
				byteArrayOutputStream.write(buffer);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return byteArrayOutputStream.toByteArray();
	}
	//endregion ****************************************************** 工具方法 ******************************************************


}
